/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package CSP;

/**
 *
 * @author 3mara
 */
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import java.util.Scanner;
import javax.imageio.ImageIO;

/**
 *
 * @author Ahmed
 */
public abstract class CoverageHeuristic extends Component {


    String OUTPUT_DIR = "output_Z/" + this.getClass().getSimpleName() + "/";
    ArrayList<Rectangle> obstacles = new ArrayList<Rectangle>();
    ArrayList<Point2D.Double> cameras = new ArrayList<Point2D.Double>();
    ArrayList<Point2D.Double> people = new ArrayList<Point2D.Double>();
    int number_of_cameras;
    int number_of_men;
    double field_of_view = Math.PI / 4; // the angle of camera range
    int number_of_orientations = 8;
    int number_of_zooms = 4;
    double delta_angle;
    boolean[][][][] spotted; // [cameraIndex][orientationIndex][personIndex]=
    // spotted
    int[] assignment;
    int[] zoomAssignment;
    double[] screenRatio;
    double nearZRadius = 0.5;
    double sectPer = 0;
    int[] IDs;
    int[] focusArray;
    int[] personCamera;
    int[] bestCoverageArray;
    FOV[] cameraIsoVist;
    double[] camDistance;
    Random rand;
    String fileName, mapName;
    boolean continuous = false;
    double[] zooms = {0.25, 0.166667, 0.111111111111, 0.083333333333333};
    int[] zoomRanges0 = {505, 300, 500, 500};
    int[] zoomRanges1 = {505, 173, 210, 135};

    abstract public int[] reassign();

    public CoverageHeuristic() {
    }

    public CoverageHeuristic(String fileName, int number_of_orientations) {
        this.number_of_orientations = number_of_orientations;
        this.delta_angle = 2 * Math.PI / number_of_orientations;
        setup(fileName);
        computeSpotted();
    }

    public CoverageHeuristic(ArrayList<Rectangle> obstacles,
            ArrayList<Point2D.Double> cameras,
            ArrayList<Point2D.Double> people, int number_of_orientations,
            boolean isCont) {
        this.obstacles = obstacles;
        this.cameras = cameras;
        this.people = people;
        this.number_of_cameras = cameras.size();
        this.number_of_men = people.size();
        this.number_of_orientations = number_of_orientations;
        // /*WORK WITHOUT OBSTACLES*/
        // this.obstacles.clear();
        this.delta_angle = 2 * Math.PI / number_of_orientations;
        this.continuous = isCont;
        // System.out.println(continuous);
    }

    final void setup(String filename) {
        try {
            File fFile = new File(filename);
            this.fileName = fileName;
            this.mapName = fFile.getName();
            // System.out.print(mapName+" : ");
            Scanner scanner = new Scanner(new FileReader(fFile));
            int number_of_obstacles = scanner.nextInt();
            number_of_cameras = scanner.nextInt();
            number_of_men = scanner.nextInt();

            try {
                // parse obstacles
                for (int i = 0; i < number_of_obstacles; i++) {
                    obstacles.add(new Rectangle(scanner.nextInt(), // The X coordinate of the  upper-left  corner of  the  Rectangle
                            scanner.nextInt(), // The Y coordinate of the
                            // upper-left corner of the  Rectangle
                            scanner.nextInt(), // width
                            scanner.nextInt())); // height
                }

                // parse cameras
                for (int i = 0; i < number_of_cameras; i++) {
                    cameras.add(new Point2D.Double(scanner.nextInt(), scanner.nextInt()));// (x,y) of camera
                }

                // parse people
                for (int i = 0; i < number_of_men; i++) {
                    people.add(new Point2D.Double(scanner.nextInt(), scanner.nextInt()));
                }
                rand = new Random(scanner.nextLong());

                personCamera = new int[number_of_men];
                cameraIsoVist = new FOV[number_of_cameras];
                camDistance = new double[number_of_men];

            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public void saveImage(String mapName, int[] assignment, int seq, int[] zoomAssignment) {
        try {
            BufferedImage viz = new BufferedImage(Test.width,
                    Test.height, BufferedImage.TYPE_INT_ARGB);
            paint(viz.getGraphics());
            paintAssignment(viz.getGraphics(), assignment, zoomAssignment);

            new File(OUTPUT_DIR).mkdirs();
            File outputfile = new File(OUTPUT_DIR + mapName + "_" + seq + "_"
                    + Arrays.toString(assignment) + ".png");
            ImageIO.write(viz, "png", outputfile);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public void saveImage(int mapName, int[] assignment, int seq, int[] zoomAssignment) {
        try {
            BufferedImage viz = new BufferedImage(Test.width,
                    Test.height, BufferedImage.TYPE_INT_ARGB);
            paint(viz.getGraphics());
            paintAssignment(viz.getGraphics(), assignment, zoomAssignment);

            new File(OUTPUT_DIR).mkdirs();
            File outputfile = new File(OUTPUT_DIR + mapName + "_" + seq + "_"
                    + Arrays.toString(assignment) + ".png");
            ImageIO.write(viz, "png", outputfile);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    FOV getOrientationIsovist(int camInd, int orient) {
        return new FOV(camInd, orient, cameras, delta_angle);
    }

    FOV getAngleIsovist(int camInd, double angle, int range0, int range1) {
        return new FOV(camInd, angle, cameras, range0, range1);

    }

    public int computeCoverage() {
        int ret = 0;
        boolean coveredArray[] = new boolean[number_of_men];
        for (int camId = 0; camId < number_of_cameras; camId++) {
            for (int i = 0; i < number_of_men; i++) {
                if (!coveredArray[i] && spotted[camId][zoomAssignment[camId]][assignment[camId]][i]) {
                    ret++;
                    coveredArray[i] = true;
                }
            }
        }
        return ret;
    }

    final void computeSpotted() {
        // System.out.println(continuous);
        spotted = new boolean[number_of_cameras][number_of_zooms][number_of_orientations][number_of_men];
        for (int camInd = 0; camInd < number_of_cameras; camInd++) { // loop
            // over  CAMERAS
            for (int zoom = 0; zoom < number_of_zooms; zoom++) {
                for (int orient = 0; orient < number_of_orientations; orient++) { // loop
                    // over ORIENTATIONS
                    FOV pol = getOrientationIsovist(camInd, orient);
                    for (int personInd = 0; personInd < number_of_men; personInd++) { // loop
                        // over
                        double tolerance = 0.00; // PEOPLE
                        for (int i = -1; i < 2; i++) {
                            double xx = people.get(personInd).x;
                            double yy = people.get(personInd).y;
                            xx += i * tolerance;
                            yy += i * tolerance;
                            Point2D.Double p = new Point2D.Double(xx, yy);
                            // if the camera can spot the person, with the right
                            // angle,
                            // and no obstacle inbetween
                            if (picks(camInd, personInd) && pol.contains(p)) {
                                spotted[camInd][zoom][orient][personInd] = true;
                            }
                        }
                    }
                }
            }
        }
    }

    boolean picks(int cam, int p) {
        for (Rectangle obs : obstacles) {
            if (obs.intersectsLine(new Line2D.Float(cameras.get(cam), people.get(p)))) {
                return false;
            }
        }
        return true;
    }

    @Override
    public void paint(Graphics g) {
        clear(g);

        Graphics2D _g = (Graphics2D) g;

        _g.setColor(Color.gray);
        _g.fillRect(0, 0, Test.width, Test.height);

        // draw obstacles
        _g.setColor(Color.black);
        for (Rectangle obs : obstacles) {
            _g.fill(obs);
        }
        // draw cameras
        Color[] array = {Color.YELLOW, Color.BLACK, Color.WHITE, Color.darkGray};
//        _g.setColor(Color.yellow);

        int i = 0;
        for (Point2D.Double cam : cameras) {
            _g.setColor(array[i]);
            i = (i + 1) % 4;
            _g.fill(new Rectangle2D.Double(cam.x - 5, cam.y - 5, 10, 10));
        }
        // draw people
        i = 0;
        for (Point2D.Double man : people) {
            _g.setColor(Color.red);
            _g.fill(new Rectangle2D.Double(man.x - 5, man.y - 5, 10, 10));

            i++;
        }
    }

    public void paintAssignment(Graphics g, int[] assignment, int[] zoomAssignment) {
        Graphics2D _g = (Graphics2D) g;
        screenRatio = new double[number_of_men];
        for (int camIndex = 0; camIndex < number_of_cameras; camIndex++) {
            if (assignment[camIndex] < 0) {
                continue;
            }
            _g.setColor(Color.ORANGE);

            int camX = (int) cameras.get(camIndex).x;
            int camY = (int) cameras.get(camIndex).y;
            if (continuous) {
                Point2D.Double[] pArr = {new Point2D.Double(camX, camY),
                    new Point2D.Double(camX + 710, camY),
                    new Point2D.Double(camX + zoomRanges0[zoomAssignment[camIndex]], camY + zoomRanges1[zoomAssignment[camIndex]])
                };
                AffineTransform t = new AffineTransform();
                double angle = Geometry.computeAngle(
                        new double[]{cameras.get(camIndex).x, cameras.get(camIndex).y, 0},
                        new double[]{people.get(assignment[camIndex]).x, people.get(assignment[camIndex]).y, 0},
                        new double[]{640, cameras.get(camIndex).y, 0});
                if (cameras.get(camIndex).y > people.get(assignment[camIndex]).y) {
                    angle = 2 * Math.PI - angle;
                }
                t.rotate(angle, camX, camY);

                t.transform(pArr, 0, pArr, 0, 3); // applying transformation to the FOV triangle

                int[] xpoints = {(int) pArr[0].x, (int) pArr[1].x,
                    (int) pArr[2].x};
                int[] ypoints = {(int) pArr[0].y, (int) pArr[1].y,
                    (int) pArr[2].y};
                for (int i = 0; i < 3; i++) {
                    pArr[i].x = xpoints[i];
                    pArr[i].y = ypoints[i];
                }

                Line2D.Float r1 = clippedLine(
                        new Line2D.Float(pArr[0], pArr[1]), obstacles);
                Line2D.Float r2 = clippedLine(
                        new Line2D.Float(pArr[0], pArr[2]), obstacles);




                _g.setStroke(new BasicStroke(3));
                _g.draw(r1);
                _g.draw(r2);
            } else {
                Point2D.Double[] pArr = {new Point2D.Double(camX, camY),
                    new Point2D.Double(camX + 710, camY),
                    new Point2D.Double(camX + zoomRanges0[zoomAssignment[camIndex]], camY + zoomRanges1[zoomAssignment[camIndex]])
                };
                AffineTransform t = new AffineTransform();

                t.rotate(assignment[camIndex] * delta_angle, camX, camY);

                t.transform(pArr, 0, pArr, 0, 3); // applying transformation to the FOV triangle

                int[] xpoints = {(int) pArr[0].x, (int) pArr[1].x,
                    (int) pArr[2].x};
                int[] ypoints = {(int) pArr[0].y, (int) pArr[1].y,
                    (int) pArr[2].y};
                for (int i = 0; i < 3; i++) {
                    pArr[i].x = xpoints[i];
                    pArr[i].y = ypoints[i];
                }

                Line2D.Float r1 = clippedLine(new Line2D.Float(pArr[0], pArr[1]), obstacles);
                Line2D.Float r2 = clippedLine(new Line2D.Float(pArr[0], pArr[2]), obstacles);




                _g.setStroke(new BasicStroke(3));
                _g.draw(r1);
                _g.draw(r2);
            }
            Ellipse2D cir = new Ellipse2D.Double();
            cir.setFrameFromCenter(camX, camY, camX - 50, camY - 50);
            _g.setColor(Color.WHITE);
            _g.draw(cir);
//            Ellipse2D cir2 = new Ellipse2D.Double();
//            cir2.setFrameFromCenter(camX, camY, camX - 300, camY - 500);
//            _g.setColor(Color.WHITE);
//            _g.draw(cir2);
            _g.setStroke(new BasicStroke(1));
            Point2D.Double cam = cameras.get(camIndex);
            for (int man = 0; man < number_of_men; man++) { // loop over people
                if (spottedFunction(camIndex, assignment[camIndex], zoomAssignment[camIndex], man)) {
                    _g.setColor(Color.GREEN);
                    _g.draw(new Line2D.Float(cam, people.get(man)));
                    _g.setColor(Color.BLUE);
                    _g.fill(new Rectangle2D.Float((int) people.get(man).x - 5,
                            (int) people.get(man).y - 5, 10, 10));
                    double temp = ((cam.distance(people.get(man)) - nearZRadius) / cam.distance(people.get(man))) * 10;
                    double totalnearZ = zooms[zoomAssignment[camIndex]] * Math.PI * nearZRadius;
//                    System.out.println(totalnearZ);
                    temp /= totalnearZ;
//                    System.out.println(temp);
                    if (screenRatio[man] < temp && temp > 0) {
                        screenRatio[man] = temp;
                    }

                }
            }

            _g.setColor(Color.WHITE);
            _g.drawString(IDs[camIndex] + "", (int) cameras.get(camIndex).x,
                    (int) cameras.get(camIndex).y);
            int i = 0;
            for (Point2D.Double man : people) {
                _g.setColor(Color.white);
                _g.drawString(i + "", (int) man.x, (int) man.y);
                i++;
            }
        }
    }

    public boolean spottedFunction(int cam, int orient, int zoom, int p) {
        return spotted[cam][zoom][orient][p];
    }

    protected void clear(Graphics g) {
        super.paint(g);
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(Test.width, Test.height);
    }

    static public Point2D.Double lineLineIntersection(Line2D.Float a,
            Line2D.Float b) {
        if (!a.intersectsLine(b)) {
            return null;
        }
        float delta = (a.x1 - a.x2) * (b.y1 - b.y2) - (a.y1 - a.y2)
                * (b.x1 - b.x2);
        float x = (a.x1 * a.y2 - a.y1 * a.x2) * (b.x1 - b.x2)
                - (b.x1 * b.y2 - b.y1 * b.x2) * (a.x1 - a.x2);
        float y = (a.x1 * a.y2 - a.y1 * a.x2) * (b.y1 - b.y2)
                - (b.x1 * b.y2 - b.y1 * b.x2) * (a.y1 - a.y2);
        return new Point2D.Double(x / delta, y / delta);
    }

    static public Point2D.Double lineRectIntersection(Line2D.Float r,
            Rectangle rect) {
        Point2D.Double p = null, x = null, y;
        PathIterator pit = rect.getPathIterator(null);
        float[] a = new float[6];
        while (!pit.isDone()) {
            switch (pit.currentSegment(a)) {
                case PathIterator.SEG_MOVETO:
                    x = new Point2D.Double(a[0], a[1]);
                    break;
                case PathIterator.SEG_LINETO:
                    y = new Point2D.Double(a[0], a[1]);
                    Point2D.Double q = lineLineIntersection(r, new Line2D.Float(x,
                            y));
                    if (q != null
                            && (p == null || r.getP1().distance(q) < r.getP1().distance(p))) {
                        p = q;
                    }
                    x = y;
                    break;
            }
            pit.next();
        }
        return p;
    }

    static public Line2D.Float clippedLine(Line2D.Float line,
            ArrayList<Rectangle> obstacles) {
        for (Rectangle obs : obstacles) { // loop over obstacles
            Point2D.Double p1 = lineRectIntersection(line, obs);
            if (p1 != null
                    && line.getP1().distance(p1) < line.getP1().distance(
                    line.getP2())) {
                line = new Line2D.Float(line.getP1(), p1);
            }
        }
        return line;
    }

    public abstract String toString();
}
